﻿using Alimentador.CustomControls;
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace Alimentador.Validations
{
    [Serializable()]
    public class Validator
    {
        private const string VALIDATOR = "38dd71AAAAmm65DD49aaaaMM2";
        private const string FILE_PATH = "C:\\Program Files\\Alimentador";

        private string m_filePath = "";
        private PApwd m_responseCode = null;
        private bool m_askForKey = false;
        private byte[] m_papwdByteArr = null;
        private DateTime m_appDate;

        public const string WARNING_MESSAGE = "Caro(a) formando(a)\nFoi excedida a solicitação de contra senha de acesso ao CD- Alimentador-IPS, deve contactar o centro de formação.";
        public const string WARNING_MESSAGE_KEY = "Chave caducada. Por favor volte a pedir a sua chave na sua página de aluno.";
        public const string WARNING_MESSAGE_APP = "Obrigado por inserir a chave.\nEsta chave será válida até ao dia: ";

        public bool m_askForPwd = false;

        /// <summary>
        /// Constructor
        /// </summary>
        public Validator()
        {
            m_filePath = FILE_PATH + "\\Alimentador.rbr";
        }

        /// <summary>
        /// Event handler
        /// </summary>
        /// <param name="responseCode">PApwd</param>
        private void validate_ResponseCodeEvent(PApwd responseCode)
        {
            m_responseCode = responseCode;
        }

        /// <summary>
        /// Check for key file, ask or validade license
        /// </summary>
        /// <returns>bool</returns>
        public bool CheckResponseCode()
        {
            try
            {
                // Second time around
                if (m_askForPwd)
                {
                    m_askForKey = true;
                }
                else
                {
                    // If file does not exist, ask for key validation
                    m_askForKey = !File.Exists(m_filePath);
                }

                bool result = false;
                if (m_askForKey)
                {
                    ValidateCtrl validate = new ValidateCtrl();
                    validate.ResponseCodeEvent += validate_ResponseCodeEvent;
                    validate.ShowDialog();
                }
                else
                {
                    m_papwdByteArr = System.IO.File.ReadAllBytes(m_filePath);
                    m_responseCode = (PApwd)ByteArrayToObject(m_papwdByteArr);
                    if (m_responseCode != null)
                    {
                        System.Console.WriteLine("Read object: " + m_responseCode.studentNumber);
                    }

                    if (m_responseCode == null)
                    {
                        File.Delete(m_filePath);         // #BP uncomnent this
                        result = false;   // Error on file
                    }
                }

                result = ValidateResposeCode();

                if (m_askForKey)
                {
                    // No file and key failed
                    string msg = WARNING_MESSAGE_KEY;

                    // No file and correct code
                    if (result)
                    {
                        msg = WARNING_MESSAGE_APP + m_appDate.ToShortDateString();
                    }
                    DisplayMsg(msg);
                }
                else
                {
                    // Has file but validation failed.
                    if (!result)
                    {
                        DisplayMsg(WARNING_MESSAGE_KEY);
                        m_askForPwd = true;
                    }
                }

                return result;
            }
            catch (Exception ex)
            {
                System.Console.WriteLine("EXCEPTION: " + ex.Message + " | " + ((ex.InnerException != null) ? ex.InnerException.Message : ""));
            }

            System.Console.WriteLine("Read object ERROR");
            return false;
        }

        private void DisplayMsg(string msg)
        {
            WarningPopUp wpp = new WarningPopUp();
            wpp.YesBtn.Visibility = System.Windows.Visibility.Collapsed;
            wpp.Height = 250;
            wpp.Width = 450;
            wpp.NoBtn.Content = "OK";
            wpp.SetMessage = msg;
            wpp.Title = "Aviso";
            wpp.NoBtn.Focus();
            wpp.ShowDialog();
        }

        /// <summary>
        /// Create the file wiht the given code values.
        /// </summary>
        /// <returns>bool</returns>
        private bool CreateFile()
        {
            try
            {
                byte[] b = ObjectToByteArray(m_responseCode);

                if (!File.Exists(FILE_PATH))
                {
                    Directory.CreateDirectory(FILE_PATH);
                }

                System.IO.File.WriteAllBytes(m_filePath, b);
                
                System.Console.WriteLine("Wrote object: " + m_responseCode.studentNumber); // #BP remove this

                return true;
            }
            catch (Exception ex)
            {
                System.Console.WriteLine("EXCEPTION: " + ex.Message + " | " + ((ex.InnerException != null) ? ex.InnerException.Message : ""));
            }

            System.Console.WriteLine("Wrote object ERROR ");
            return false;
        }

        /// <summary>
        /// Cehck if response code is ok.
        /// </summary>
        /// <returns>bool</returns>
        private bool ValidateResposeCode()
        {
            bool res = false;

            try
            {
                DateTime dt = DateTime.Now;

                string enterCode = m_responseCode.enterCode;
                DateTime keyDate = GetDateFromCode(enterCode.Substring(2, 2) + enterCode.Substring(10, 2) + enterCode.Substring(18, 4));
                m_appDate = GetDateFromCode(enterCode.Substring(14, 2) + enterCode.Substring(22, 2) + enterCode.Substring(6, 4));

                // Se pediu chave valida data de pedido
                if (m_askForKey)
                {
                    keyDate = keyDate.AddDays(1);
                    res = dt <= keyDate;
                }
                else // Não valida chave de pedido
                {
                    res = true;
                }

                res = res && (m_appDate >= dt);
                res = res && ValidateStudentNumberFromCode(enterCode.Substring(0, 2) + enterCode.Substring(4, 2) + enterCode.Substring(12, 2) + enterCode.Substring(16, 2) + enterCode.Substring(24, 1));

                if (res && m_askForKey) // Create file if valid code
                {
                    res = CreateFile();
                }

                return res;
            }
            catch (Exception ex)
            {
                System.Console.WriteLine("EXCEPTION: " + ex.Message + " | " + ((ex.InnerException != null) ? ex.InnerException.Message : ""));
                return false;
            }
        }

        /// <summary>
        /// Retrieve the KeyDate from the given enter code.
        /// </summary>
        /// <returns></returns>
        private DateTime GetDateFromCode(string dateStr)
        {
            try
            {
                // Convert letters to numbers
                dateStr = ConvertFromCode(dateStr);
                
                // return newly created date
                return new DateTime(int.Parse(dateStr.Substring(4, 4)), int.Parse(dateStr.Substring(2, 2)), int.Parse(dateStr.Substring(0, 2)));
            }
            catch (Exception ex)
            {
                System.Console.WriteLine("EXCEPTION: " + ex.Message + " | " + ((ex.InnerException != null) ? ex.InnerException.Message : ""));
                return DateTime.MinValue;
            }
        }

        /// <summary>
        /// Validate if the given student number matchs the given key student number
        /// </summary>
        /// <param name="enter">string</param>
        /// <returns>bool</returns>
        private bool ValidateStudentNumberFromCode(string enter)
        {
            bool res = false;

            try
            {
                // Convert input code to correct sequence
                string n = "" + enter[3] + enter[8] + enter[0] + enter[6] + enter[5] + enter[4] + enter[2] + enter[1] + enter[7];
                // Convert input code into number
                string num = ConvertFromCode(n);

                int numCode = int.Parse(num);
                int numEnter = int.Parse(m_responseCode.studentNumber);

                // Check if number is corrected
                res = numCode == numEnter;
            }
            catch (Exception ex)
            {
                System.Console.WriteLine("EXCEPTION: " + ex.Message + " | " + ((ex.InnerException != null) ? ex.InnerException.Message : ""));
                res = false;
            }

            return res;
        }

        /// <summary>
        /// Convert according to existing code.
        /// </summary>
        /// <param name="enter">string</param>
        /// <returns>string</returns>
        private string ConvertFromCode(string enter)
        {
            string res = "";

            foreach (char c in enter)
            {
                switch (c)
                {
                    case 'A': res += "3"; break;
                    case 'B': res += "5"; break;
                    case 'C': res += "8"; break;
                    case 'D': res += "0"; break;
                    case 'E': res += "6"; break;
                    case 'F': res += "4"; break;
                    case 'G': res += "1"; break;
                    case 'H': res += "0"; break;
                    case 'K': res += "5"; break;
                    case 'M': res += "4"; break;
                    case 'N': res += "2"; break;
                    case 'P': res += "2"; break;
                    case 'Q': res += "7"; break;
                    case 'R': res += "1"; break;
                    case 'S': res += "9"; break;
                    case 'T': res += "9"; break;
                    case 'W': res += "6"; break;
                    case 'X': res += "3"; break;
                    case 'Y': res += "8"; break;
                    case 'Z': res += "7"; break;
                }
            }

            return res;
        }

        /// <summary>
        /// Convert Object to Byte Array
        /// </summary>
        /// <param name="obj">obj</param>
        /// <returns>byte[]</returns>
        private byte[] ObjectToByteArray(Object obj)
        {
            if (obj == null)
            {
                return null;
            }

            try
            {
                using (MemoryStream ms = new MemoryStream())
                {
                    BinaryFormatter bf = new BinaryFormatter();
                    bf.Serialize(ms, obj);
                    return ms.ToArray();
                }
            }
            catch
            {
                // Do nothing. Log when return is null on the caller's side.
            }

            return null;
        }

        /// <summary>
        /// Convert given byte[] to Object
        /// </summary>
        /// <param name="arrBytes">byte[]</param>
        /// <returns>Object</returns>
        private Object ByteArrayToObject(byte[] arrBytes)
        {
            try
            {
                using (MemoryStream memStream = new MemoryStream())
                {
                    BinaryFormatter binForm = new BinaryFormatter();
                    memStream.Write(arrBytes, 0, arrBytes.Length);
                    memStream.Seek(0, SeekOrigin.Begin);
                    Object obj = (Object)binForm.Deserialize(memStream);
                    return obj;
                }
            }
            catch
            {
                // Do nothing. Log when return is null on the caller's side.
            }

            return null;
        }
    }
}
